package nqueens.algorithm;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import nqueens.config.GAProperties;
import nqueens.core.Chromosome;
import nqueens.core.Population;
import nqueens.exception.GeneticOperatorMissingException;
import nqueens.util.Debugger;

public class GeneticAlgorithm {

	private ISelectionAlgorithm selector;

	private ICrossoverAlgorithm cross;

	private IMutationAlgorithm mutator;

	private GAProperties params;

	Set<Chromosome> partialSolutions;
	
	private int generationsCounter;
	
	public GeneticAlgorithm() {
		this(null, null, null);
	}

	public GeneticAlgorithm(ISelectionAlgorithm selector,
			ICrossoverAlgorithm cross, IMutationAlgorithm mutator) {
		this.selector = selector;
		this.cross = cross;
		this.mutator = mutator;
		partialSolutions = new HashSet<Chromosome>();
		params = GAProperties.getInstance();
	}

	public ISelectionAlgorithm getSelector() {
		return selector;
	}

	public void setSelector(ISelectionAlgorithm selector) {
		this.selector = selector;
	}

	public ICrossoverAlgorithm getCross() {
		return cross;
	}

	public void setCross(ICrossoverAlgorithm cross) {
		this.cross = cross;
	}

	public IMutationAlgorithm getMutator() {
		return mutator;
	}

	public void setMutator(IMutationAlgorithm mutator) {
		this.mutator = mutator;
	}

	public void run() {
		partialSolutions.clear();
		Debugger.getInstance().print("Inicializando algoritmo..");
		if (selector == null || cross == null || mutator == null) {
			throw new GeneticOperatorMissingException(
					"Genetic operator is missing.");
		}
		int maxGenerations = params.getInteger(GAProperties.MAX_GENERATIONS);

		generationsCounter = 0;
		Population currGeneration = selector.initPopulation();
		Debugger.getInstance().print("initialGeneration = " + currGeneration);
		while (!isFinished(generationsCounter, maxGenerations)) {
			Population nextGeneration = selector.select(currGeneration);
			Debugger
					.getInstance()
					.print(
							"==============================================================");
			Debugger.getInstance().print(
					"generationsCounter = " + generationsCounter);
			Debugger.getInstance().print(
					"solutionsCounter = " + partialSolutions.size());
			
			cross.crossover(nextGeneration);
			mutator.mutate(nextGeneration);
			Debugger.getInstance().print("nextGeneration = " + nextGeneration);
			Debugger.getInstance().print(nextGeneration.maxObjective()+"");
			processPopulation(nextGeneration);
			currGeneration = nextGeneration;
			generationsCounter++;
		}
	}

	private boolean isFinished(int gen, int max) {
		return partialSolutions.size() > 0 || gen >= max;
	}

	private void processPopulation(Population pop) {
		Iterator<Chromosome> it = pop.iterator();
		while (it.hasNext()) {
			Chromosome next = it.next();
			if (!partialSolutions.contains(next) && next.isSolution()) {
				partialSolutions.add((Chromosome) next.clone());
			}
		}
	}

	// Retorna true se o dado individuo eh uma solucao para o problema das
	// N-Rainhas

	public Iterator<Chromosome> solutionsIterator() {
		return partialSolutions.iterator();
	}
	
	public int getGenerationsCounter() {
		return generationsCounter;
	}
}
